from generate_common import handleCustomApply
import math

# ============ 主函数 ============
def genItemNum(ruleDatum, ruleAct, filterDatum, hsp_abbr):
    # 获得 inOne，默认是 case，也可是 day, hour, year, patient
    inOne = filterDatum.get('in_one_', 'case')
    if inOne not in ('case', 'day', 'hour', 'year', 'patient', 'default', ''):
        raise ValueError(f'item num，in_one_ 的值 {inOne} 不合法')
    
    # 获得 of，默认是 ['trigger']，并且处理
    of = filterDatum.get('of', ['trigger'])
    itemObjApply = []
    itemNames = []
    if 'trigger' in of:
        # 从ruleAct中获取 trigger 与 trigger_apply
        itemObjApply += ruleAct.get('trigger_apply', [])
        itemNames += filterDatum.get('trigger', [])
    if 'item' in of:
        # 遍历ruleAct['filter']，如果算子的field是'item'，则将value加上去
        for filterDatumItem in ruleAct.get('filter', []):
            if filterDatumItem.get('field') == 'item':
                itemObjApply += filterDatumItem.get('value_apply', [])
                itemNames += filterDatumItem.get('value', [])
    if 'itemextra' in of:
        # 从ruleAct中获取 itemextra 与 itemextra_apply
        itemObjApply += ruleAct.get('itemextra_apply', [])
        itemNames += filterDatum.get('itemextra', [])
    # 对itemObjApply、itemNames制作sql语句。这里的item就是要比较数量的项目
    itemCodesSql, itemNamesSql = handleCustomApply(itemObjApply, itemNames, type='item', tblAbbr='d')
    
    # 获得 by，默认是 default，也可是是 avg, min, max, sum（统称为 aggr）
    by = filterDatum.get('by', 'default')
    if by not in ('default', 'avg', 'min', 'max', 'sum', 'aggr'):
        raise ValueError(f'item num，by 的值 {by} 不合法')
    
    # 获得 than，默认是 absolute，也可是是 period, item
    than = filterDatum.get('than', 'absolute')
    if than not in ('absolute', 'period', 'item'):
        raise ValueError(f'item num，than 的值 {than} 不合法')
    
    # 获得 operator，是 gt, lt, eq, gte, lte
    operator = filterDatum['operator']
    operSql = ""
    if operator == 'gt':
        operSql = ">"
    elif operator == 'lt':
        operSql = "<"
    elif operator == 'eq':
        operSql = "="
    elif operator == 'gte':
        operSql = ">="
    elif operator == 'lte':
        operSql = "<="
    else:
        raise ValueError(f'item num，operator 的值 {operator} 不合法')
    
    # 获得 value，默认是 0。如果 than 是 absolute，那么 value 应该是一个数字；如果 than 是 item，那么 value 应该是一个项目列表
    value = filterDatum.get('value', 0)
    if than == 'absolute':
        try:
            value = float(value)
            if value == math.floor(value):
                value = int(value)
        except:
            raise ValueError(f'item num，than=absolute 的时候，value 的值 {value} 不是数字')
    elif than == 'item':
        if not isinstance(value, list):
            raise ValueError(f'item num，than=item 的时候，value 的值 {value} 不是列表')
        valueApply = filterDatum['value_apply']
        # 对 value 制作 sql 语句
        valueSql, _ = handleCustomApply(valueApply, [], type='item', tblAbbr='d')

    # 获得 plus_before_times, times, plus_after_times，针对 period 的修正
    plusBeforeTimes = filterDatum.get('plus_before_times', 0)
    times = filterDatum.get('times', 1)
    plusAfterTimes = filterDatum.get('plus_after_times', 0)

    """
    在这个流程中，将会用到 vb 与 vb2。其中 vb 用于汇总统计 of 项目的数量；vb2 用于辅助计算数量阈值，用 vb2.num1 这个字段来存放数量阈值。
    这个流程当中，除了关联键以外，将会有 thanKeys 的概念，是不同的 than 方式所需要的、专属于 than 的关联键。
    
    ============ 思路 ============
    1. 统计数量汇总。
        a. 如果 of==['trigger']， 根据 in_one_，确定关联键；从 va 的关联键中汇总统计项目数量，放在 vb 当中
        b. 如果 of 有其他，那么从 va 的关联键，关联 d 表，汇总统计项目数量，放在 vb 当中
    2. 统计数量阈值。
        a. 如果 than=absolute，则与一个确定的绝对值比较。vb2 就一行一列，num1thanKey = []
        b. 如果 than=period，则从 va 的 setl_id 中关联 m 表，查 period，放在 vb2 表中。vb2 除了存放 period，将 period 计算出来的数量阈值放在 num1thanKey = [ selt_id ]
        c. 如果 then=item，则从 va 的关联键中，关联 d 表，统计 item 的数量，求总数量，放在 vb2 表中。vb2 除了存放 sum_q，也将 sum_q 计算出来的数量阈值放在 num1在这个时候，也要统计 item_j_code, item_j_name, ... 等信息，等待放入关联信息thanKey = 关联键
    3. 删除 vb 的不违规部分。关联 vb 与 vb2，用 vb 的 q 与 vb2 的 num1 比较，删掉不违规的部分
    4. 删除 va 的不违规部分。删掉 va 当中不在 vb 的部分
    5. 添加 accu_q（仅当of==['trigger']的时候）。va 根据关联键，按照最小金额纳入的原则，计算 accu_q
        a. 如果 operator 是小于或者小于等于，那么按 p 升序，d0_rowid 升序排序
        b. 如果 operator 是大于或者大于等于，那么按 p 降序，d0_rowid 升序排序
    6. 更新 va 的数量阈值（仅当 of==['trigger'] 的时候）。关联 va 与 vb2，将 vb2 的 num1 更新到 va 的 num1 当中
    7. 计算 va 的违规数量（仅当 of==['trigger'] 的时候）。通过 va 的 accu_q 与 num1，计算 vlt_q
        a. 如果 operator 是小于或者小于等于，那么accu_q 小于 num1 的部分（即 num1-accu_q）就是违规数量，但是不能大于 q，也不能小于 0
        b. 如果 operator 是大于或者大于等于，那么 accu_q 大于 num1 的部分（即 accu_q-num1）就是违规数量，但是不能大于 q，也不能小于 0
    8. 更新关联信息（仅当 than==item 的时候）。如果 than=item，则额外将 vb2 中的关联信息更新到 va 当中
    9. 更新文字关联信息 related。如果 in_one_不是 day 或者 hour，那么加一个文字说明："病例内"/“年度内”/“数据范围内" 已使用数量： {num1} 
    
    ============ 参数 ============
    ● in_one_：case、day、hour、year、patient
        ○ case：关联键是 setl_id
        ○ day：关联键是 setl_id, item_day
        ○ hour：关联键是 setl_id, item_day, item_hour
        ○ year：关联键是 psn_no, j_year
        ○ patient：关联键是 psn_no
    ● of：一个列表，里面写明了要统计的数量的种类，默认是['trigger']，也可能有 itemextra
    ● by：default、aggr
    ● than：abs、period、item
    ● operator：大于、小于、等于
    ● value：
        ○ 如果是 than abs，就是一个数字
        ○ 如果是 than item，就是项目列表
    ● plus_before_times, times, plus_after_times，针对 period 的修正
    """
    
    # 制作关联键，关联键就是用于分类汇总以及两表关联的字段列表
    if inOne == 'case' or inOne == '' or inOne == 'default':
        joinKeys = ['setl_id']
    elif inOne == 'day':
        joinKeys = ['setl_id', 'item_day']
    elif inOne == 'hour':
        joinKeys = ['setl_id', 'item_day', 'item_hour']
    elif inOne == 'year':
        joinKeys = ['psn_no', 'j_year']
    elif inOne == 'patient':
        joinKeys = ['psn_no']
    else:
        raise ValueError(f'item num，in_one_ 的值 {inOne} 不合法')
    
    # 判断 by，如果 by 是default，那么说明每个特定项目单独统计数量，那么关联键就要加上 item_j_code
    if by == 'default':
        joinKeys.append('item_j_code')
        
    sqlList = []
    
    # 清空临时表
    sqlList.append("TRUNCATE TABLE vlt_tmp_b")
    sqlList.append("TRUNCATE TABLE vlt_tmp_b2")
    
    # step1，统计数量汇总
    if of == ['trigger']:
        # 仅仅是 trigger，那么直接从 va 统计数量
        sql1 = f"""
            -- item num，step1，统计数量汇总
            INSERT INTO vlt_tmp_b vb ({', '.join(joinKeys)}, sum_q)
            SELECT
                {', '.join([f'va.{key}' for key in joinKeys])},
                SUM(va.q) AS sum_q
            FROM vlt_tmp_a va
            GROUP BY {', '.join([f'va.{key}' for key in joinKeys])}
        """
    else:
        # of 里面有 itemextra或者其他，那么需要关联 d 表
        sql1 = f"""
            -- item num，step1，统计数量汇总
            INSERT INTO vlt_tmp_b vb ({', '.join(joinKeys)}, sum_q)
            SELECT
                {', '.join([f'd.{key}' for key in joinKeys])},
                SUM(d.q) AS sum_q
            FROM d
            WHERE EXISTS (
                SELECT 1
                FROM vlt_tmp_a va
                WHERE {' AND '.join([f'd.{key}=va.{key}' for key in joinKeys])}
            ) {itemCodesSql} {itemNamesSql}
            GROUP BY {', '.join([f'd.{key}' for key in joinKeys])}
        """
    sqlList.append(sql1)
    
    # step2，统计数量阈值
    if than == 'absolute':
        # 绝对值，那么 vb2 就一行一列
        thanKeys = []
        sql2 = f"""
            -- item num，step2a(than absolute)，统计数量阈值
            INSERT INTO vlt_tmp_b2 vb2 (num1)
            VALUES ({value})
        """
        sqlList.append(sql2)
    elif than == 'period':
        # period，那么从 va 的 setl_id 关联 m 表，查 period
        thanKeys = ['setl_id']
        sql2 = f"""
            -- item num，step2b(than period)，统计数量阈值
            INSERT INTO vlt_tmp_b2 vb2 (setl_id, period, num1)
            SELECT
                m.setl_id, m.period,
                COALESCE(m.period, 0) * {times} + {plusBeforeTimes} + {plusAfterTimes} AS num1
            FROM m
            WHERE EXISTS(
                SELECT 1
                FROM vlt_tmp_a va
                WHERE m.setl_id = va.setl_id
            )
        """
        sqlList.append(sql2)
    elif than == 'item':
        # item，那么从 va 的关联键，关联 d 表，统计 item 的数量
        thanKeys = joinKeys.copy()
        # 如果thanKeys中有 item_j_code，那么要去掉
        if 'item_j_code' in thanKeys:
            thanKeys.remove('item_j_code')
        # 如果valueApply不是空列表，就统计相应的数量；如果是空列表，就不需要统计任何数量
        if len(valueApply) > 0 and len(valueSql) > 0:
            sql2 = f"""
                -- item num，step2c(than item)，统计数量阈值
                INSERT INTO vlt_tmp_b2 vb2 (
                    {', '.join(thanKeys)}, 
                    item_j_code2, item_j_name2, item_code2, item_name2, item_med_code2, item_hsp_code2, item_hsp_name2, 
                    item_time2, item_dept2, apply_dept2, p2, q2, c2, b2,
                    sum_q, num1
                )
                SELECT
                    {', '.join([f'd.{key}' for key in thanKeys])},
                    LISTAGG(DISTINCT d.item_j_code, ',') WITHIN GROUP(ORDER BY d.item_j_code) AS item_j_code2,
                    LISTAGG(DISTINCT d.item_j_name, ',') WITHIN GROUP(ORDER BY d.item_j_code) AS item_j_name2,
                    LISTAGG(DISTINCT d.item_code, ',') WITHIN GROUP(ORDER BY d.item_j_code) AS item_code2,
                    LISTAGG(DISTINCT d.item_name, ',') WITHIN GROUP(ORDER BY d.item_j_code) AS item_name2,
                    LISTAGG(DISTINCT d.item_med_code, ',') WITHIN GROUP(ORDER BY d.item_j_code) AS item_med_code2,
                    LISTAGG(DISTINCT d.item_hsp_code, ',') WITHIN GROUP(ORDER BY d.item_j_code) AS item_hsp_code2,
                    LISTAGG(DISTINCT d.item_hsp_name, ',') WITHIN GROUP(ORDER BY d.item_j_code) AS item_hsp_name2,
                    TO_CHAR(MIN(d.item_time), 'yy-mm-dd hh24:mi:ss') || ' ~ ' || TO_CHAR(MAX(d.item_time), 'yy-mm-dd hh24:mi:ss') AS item_time2,
                    LISTAGG(DISTINCT d.item_dept, ',') WITHIN GROUP(ORDER BY d.item_j_code) AS item_dept2,
                    LISTAGG(DISTINCT d.apply_dept, ',') WITHIN GROUP(ORDER BY d.item_j_code) AS apply_dept2,
                    LISTAGG(DISTINCT ROUND(d.p,2), ',') WITHIN GROUP(ORDER BY d.item_j_code) AS p2,
                    SUM(d.q) AS q2, SUM(d.c) AS c2, SUM(d.b) AS b2,
                    SUM(d.q) AS sum_q,
                    ({plusBeforeTimes} + SUM(d.q)) * {times} + {plusAfterTimes} AS num1
                FROM d
                WHERE EXISTS(
                    SELECT 1
                    FROM vlt_tmp_a va
                    WHERE {' AND '.join([f'd.{key}=va.{key}' for key in thanKeys]) if thanKeys else '1=1'}
                ) {valueSql}
                {'GROUP BY ' + ', '.join([f'd.{key}' for key in thanKeys]) if thanKeys else ''}
            """
            sqlList.append(sql2)
    else:
        raise ValueError(f'item num，在构建step2的sql的时候，than 的值 {than} 不合法')
    
    
    # step3，删除 vb 的不违规部分
    join_condition = ' AND '.join([f'vb.{key}=vb2.{key}' for key in thanKeys]) if thanKeys else '1=1'
    sql3 = f"""
        -- item num，step3，删除 vb 的不违规部分
        DELETE FROM vlt_tmp_b vb
        WHERE NOT EXISTS(
            SELECT 1
            FROM vlt_tmp_b2 vb2
            WHERE {join_condition}
            AND vb.sum_q {operSql} vb2.num1
        )
    """
    sqlList.append(sql3)
    
    # step4，删除 va 的不违规部分
    sql4 = f"""
        -- item num，step4，删除 va 的不违规部分
        DELETE FROM vlt_tmp_a va
        WHERE NOT EXISTS(
            SELECT 1
            FROM vlt_tmp_b vb
            WHERE {' AND '.join([f'va.{key}=vb.{key}' for key in joinKeys])}
        )
    """
    sqlList.append(sql4)
    
    # 以下几个步骤，仅当 of==['trigger'] 的时候才需要，因为这个时候才涉及到违规数量的计算
    if of == ['trigger']:
        # step5，添加 accu_q（仅当of==['trigger']的时候）
        orderBy = "ASC" if operator in ('lt', 'lte') else "DESC"
        sql5 = f"""
            -- item num，step5，添加 accu_q（仅当of==['trigger']的时候）
            MERGE INTO vlt_tmp_a va
            USING (
            SELECT
                d0_rowid,
                SUM(q) OVER (
                PARTITION BY {', '.join([f'va.{key}' for key in joinKeys])}
                ORDER BY p {orderBy}, d0_rowid ASC
                ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
                ) AS accu_q
            FROM vlt_tmp_a va
            ) sub
            ON (va.d0_rowid = sub.d0_rowid)
            WHEN MATCHED THEN
            UPDATE SET va.accu_q = sub.accu_q
        """
        sqlList.append(sql5)
        
        # step6，更新 va 的数量阈值（仅当 of==['trigger'] 的时候）
        join_condition_step6 = ' AND '.join([f'va.{key}=vb2.{key}' for key in thanKeys]) if thanKeys else '1=1'
        sql6 = f"""
            -- item num，step6，更新 va 的数量阈值（仅当 of==['trigger'] 的时候）
            MERGE INTO vlt_tmp_a va
            USING vlt_tmp_b2 vb2
            ON ({join_condition_step6})
            WHEN MATCHED THEN
            UPDATE SET num1 = vb2.num1
        """
        sqlList.append(sql6)
        
        # step7，计算 va 的违规数量（仅当 of==['trigger'] 的时候）
        if operator in ('gt', 'gte'):
            sql7 = f"""
                -- item num，step7a(operator in gt, gte)，计算 va 的违规数量（仅当 of==['trigger'] 的时候）
                UPDATE vlt_tmp_a
                SET vlt_q = GREATEST(LEAST(accu_q - num1, q), 0)
            """
            sqlList.append(sql7)
        elif operator in ('lt', 'lte'):
            sql7 = f"""
                -- item num，step7b(operator in lt, lte)，计算 va 的违规数量（仅当 of==['trigger'] 的时候）
                UPDATE vlt_tmp_a
                SET vlt_q = GREATEST(LEAST(num1 - accu_q, q), 0)
            """
            sqlList.append(sql7)
    
    # step8，更新关联信息（仅当 than==item 的时候）
    if than == 'item':
        # 关联信息是指 
        join_condition_step8 = ' AND '.join([f'va.{key}=vb2.{key}' for key in thanKeys]) if thanKeys else 'va.setl_id=vb2.setl_id'
        sql8 = f"""
            -- item num，step8，更新关联信息（仅当 than==item 的时候）
            MERGE INTO vlt_tmp_a va
            USING vlt_tmp_b2 vb2
            ON ({join_condition_step8})
            WHEN MATCHED THEN
            UPDATE SET
                va.item_j_code2 = vb2.item_j_code2,
                va.item_j_name2 = vb2.item_j_name2,
                va.item_code2 = vb2.item_code2,
                va.item_name2 = vb2.item_name2,
                va.item_med_code2 = vb2.item_med_code2,
                va.item_hsp_code2 = vb2.item_hsp_code2,
                va.item_hsp_name2 = vb2.item_hsp_name2,
                va.item_time2 = vb2.item_time2,
                va.item_dept2 = vb2.item_dept2,
                va.p2 = vb2.p2,
                va.q2 = vb2.q2,
                va.c2 = vb2.c2,
                va.b2 = vb2.b2
        """
        sqlList.append(sql8)
    
    # step9，更新文字关联信息 related
    if inOne in ('case', 'year', 'patient'):
        inOneText = "病例内" if inOne == 'case' else ("年度内" if inOne == 'year' else "患者内")
        sql9 = f"""
            -- item num，step9，更新文字关联信息 related
            UPDATE vlt_tmp_a
            SET related = COALESCE(related, '') || '{inOneText}已使用数量： ' || COALESCE(num1, 0)
        """
        sqlList.append(sql9)


    return sqlList
